package com.gitee.wsl.doc.sample.opml

import com.gitee.wsl.collections.stack.stackOf
import com.gitee.wsl.doc.sample.opml.ValidityCheck.requirePosition
import com.gitee.wsl.doc.sample.opml.bean.Opml
import com.gitee.wsl.doc.xml.EventType
import com.gitee.wsl.doc.xml.MiniXmlPullParser
import com.gitee.wsl.doc.xml.XmlPullParserException
import com.gitee.wsl.text.format.format
import kotlinx.io.IOException


/**
 * Parser for OPML documents. Instances are threadsafe and reusable.
 */
class OpmlParser {
    /**
     * Parse the given [String] into an [com.gitee.wsl.doc.sample.opml.bean.Opml] instance
     *
     * @param string a [String] representation of a full and valid OPML
     * document
     * @return [com.gitee.wsl.doc.sample.opml.bean.Opml] instance, not `null`
     * @throws IllegalArgumentException if argument is `null` or invalid
     * @throws com.gitee.wsl.doc.sample.opml.OpmlParseException       if an exception occurs during parsing
     */
    @Throws(_root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException::class)
    fun parse(string: String): com.gitee.wsl.doc.sample.opml.bean.Opml {
        require(string.trim { it <= ' ' }.isNotEmpty()) { "argument can not be null or empty" }
        try {
            return extract(string)
        } catch (e: IOException) {
            throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException(e)
        }
    }

    /**
     * Parse the given [InputStream] into an [com.gitee.wsl.doc.sample.opml.bean.Opml] instance. Note that
     * the caller is responsible for closing the given [InputStream].
     *
     * @param input an [InputStream] over a valid OPML document
     * @return [com.gitee.wsl.doc.sample.opml.bean.Opml] instance, not `null`
     * @throws IllegalArgumentException if argument is `null`
     * @throws com.gitee.wsl.doc.sample.opml.OpmlParseException       if argument invalid or if an exception
     * occurs during parsing
     */
//    @Throws(OpmlParseException::class)
//    fun parse(input: InputStream?): Opml {
//        requireNotNull(input) { "argument can not be null" }
//        try {
//            InputStreamReader(input, StandardCharsets.UTF_8).use { reader ->
//                return parse(reader)
//            }
//        } catch (e: IOException) {
//            throw OpmlParseException(e)
//        }
//    }

    /**
     * Parse the given [Reader] into an [com.gitee.wsl.doc.sample.opml.bean.Opml] instance. Note that the
     * caller is responsible for closing the given [Reader].
     *
     * @param reader a [Reader] over a valid OPML document
     * @return [com.gitee.wsl.doc.sample.opml.bean.Opml] instance, not `null`
     * @throws IllegalArgumentException if argument is `null`
     * @throws com.gitee.wsl.doc.sample.opml.OpmlParseException       if argument invalid or if an exception
     * occurs during parsing
     */
//    @Throws(OpmlParseException::class)
//    fun parse(reader: Reader?): Opml {
//        requireNotNull(reader) { "argument can not be null" }
//        try {
//            return extract(reader)
//        } catch (e: XmlPullParserException) {
//            throw OpmlParseException(e)
//        } catch (e: IOException) {
//            throw OpmlParseException(e)
//        }
//    }

    @Throws(
        XmlPullParserException::class,
        IOException::class,
        _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException::class
    )
    private fun extract(reader: String): com.gitee.wsl.doc.sample.opml.bean.Opml {
        val xpp = MiniXmlPullParser(reader)

        val initHandler = _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlInitHandler()
        val headHandler = _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlHeadHandler()
        val bodyHandler = _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlBodyHandler()

        var handler: com.gitee.wsl.doc.sample.opml.OpmlSectionHandler<*> = initHandler

        val stack = stackOf<String>()
        var startedOpml = false
        var startedHead = false
        var startedBody = false

        while (xpp.eventType != EventType.END_DOCUMENT) {
            when (xpp.next()) {
                EventType.START_TAG -> {
                    val name = xpp.name
                    stack.push(name)
                    when (name) {
                        "opml" -> {
                            if (startedOpml) {
                                throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException("OPML documents can have only one opml section")
                            }
                            handler.startTag(xpp)
                            startedOpml = true
                        }

                        "head" -> {
                            if (startedHead) {
                                throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException("OPML documents can have only one head section")
                            }
                            handler = headHandler
                            startedHead = true
                        }

                        "body" -> {
                            if (startedBody) {
                                throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException("OPML documents can have only one body section")
                            }
                            handler = bodyHandler
                            handler.startTag(xpp)
                            startedBody = true
                        }

                        else -> {
                            handler.startTag(xpp)
                        }
                    }
                    requirePosition(xpp, EventType.START_TAG)
                }

                EventType.TEXT -> {
                    handler.text(xpp)
                    requirePosition(xpp, EventType.TEXT)
                }

                EventType.END_TAG -> {
                    val ended = xpp.name
                    stack.pop()
                    when (ended) {
                        "head" -> {
                            handler.endTag(xpp)
                            handler = initHandler
                        }

                        "body" -> {
                            handler.endTag(xpp)
                            handler = initHandler
                        }

                        else -> {
                            handler.endTag(xpp)
                        }
                    }
                    requirePosition(xpp, EventType.END_TAG)
                }

                else->{}
            }
        }

        if (!stack.isEmpty()) {
            throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException(
                String.format(
                    "XML invalid, unclosed tags %s",
                    stack
                )
            )
        }
        if (!startedOpml) {
            throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException("XML invalid, no <opml> element")
        }
        if (!startedHead) {
            throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException("XML invalid, no <head> element")
        }
        if (!startedBody) {
            throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException("XML invalid, no <body> element")
        }

        return _root_ide_package_.com.gitee.wsl.doc.sample.opml.bean.Opml(
            initHandler.get(),
            headHandler.get(),
            bodyHandler.get()
        )
    }

//    @Throws(XmlPullParserException::class)
//    private fun newXmlPullParser(reader: Reader?): XmlPullParser {
//        val factory: XmlPullParserFactory = XmlPullParserFactory.newInstance()
//        factory.setNamespaceAware(true)
//        val xpp: XmlPullParser = factory.newPullParser()
//        xpp.setInput(reader)
//        return xpp
//    }
}
